using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using System.Data;
using System.IO;
using System.Diagnostics;

namespace MyClever.Lib
{
    public static class MissionXMLReader
    {
        /// <summary>
        /// Returns the specifications of the selected mission, which you can set in the mssion.xml-file (Mission/Specification).
        /// Typically you can set the <see cref="Mission.Name"/> of the mission or a simple <see cref="Mission.Description"/>, what the mission does.
        /// </summary>
        public static Dictionary<string, string> GetSpecifications(ref XmlDocument missionXMLDocument)
        {
            Dictionary<string, string> specification = new Dictionary<string, string>();
            
            // get the mission specifications ...
            XmlNodeList specificationNodes = missionXMLDocument.GetElementsByTagName("Specification").Item(0).ChildNodes;
            for (int i = 0; i < specificationNodes.Count; i++)
            {
                // ... and save them
                specification.Add(specificationNodes[i].Name.ToLower(), specificationNodes[i].InnerText);
            }
            return specification;
        }

        public static MissionPluginLoopNode GetPluginCollection(ref XmlDocument missionXMLDocument)
        {
            return MissionXMLReader.GetNextPluginCollection(missionXMLDocument.SelectSingleNode("Mission/Blueprint"));
        }

        private static MissionPluginLoopNode GetNextPluginCollection(XmlNode collectionNode)
        {
            MissionPluginLoopNode collectionElements;

            if ("Blueprint" == collectionNode.Name)
            {
                collectionElements = new MissionPluginLoopNode(0, null);
            }
            else
            {
                Dictionary<string, object> loopNodeAttributes = MissionXMLReader.GetAttributes(collectionNode);
                collectionElements = new MissionPluginLoopNode(Convert.ToInt32(loopNodeAttributes["ID"]), new PackageReference(Convert.ToInt32(loopNodeAttributes["FromPluginID"]), loopNodeAttributes["FromPackage"].ToString()));
            }

            foreach (XmlNode nodeOn in collectionNode.ChildNodes)
            {
                if ("Plugin" == nodeOn.Name)
                {
                    collectionElements.Append(MissionXMLReader.GetPlugin(nodeOn));
                }
                else if ("LoopThroughEveryPackageRow" == nodeOn.Name)
                {
                    collectionElements.Append(MissionXMLReader.GetNextPluginCollection(nodeOn));
                }
            }
            return collectionElements;
        }

        private static MissionPluginProxy GetPlugin(XmlNode pluginNode)
        {
            // get all plugin parameters
            Dictionary<string, object> pluginAttributes = MissionXMLReader.GetAttributes(pluginNode);
            PluginPath pluginPath = new PluginPath(pluginAttributes["Maintainer"].ToString(), pluginAttributes["Name"].ToString(), pluginAttributes["Version"].ToString(), pluginAttributes["File"].ToString());

            MissionPluginProxy pluginProxy = new MissionPluginProxy(Convert.ToInt32(pluginAttributes["ID"]), pluginPath, pluginAttributes["Class"].ToString(), (ProgrammingLanguage)Enum.Parse(typeof(ProgrammingLanguage), pluginAttributes["Language"].ToString(), true));
            FillPackages(pluginProxy, pluginNode);
            return pluginProxy;
        }

        private static void FillPackages(MissionPluginProxy pluginProxy, XmlNode pluginNode)
        {
            // get all package-types like "Input" or "Output" by the plugin with the expected plugin id
            foreach (XmlNode packageTypeNode in pluginNode.ChildNodes)
            {
                // run through every package
                foreach (XmlNode packageOn in packageTypeNode)
                {
                    if (packageOn.NodeType != XmlNodeType.Comment)
                    {
                        // get the MissionPluginPackage for this package
                        MissionPluginPackage packageTable = pluginProxy.GetPackageByName(packageOn.Attributes.GetNamedItem("Name").Value);

                        if ("LinkedPackage" == packageOn.Name)
                        {
                            // linked packages can be a part of INPUT-packages only!
                            Debug.Assert("Input" == packageTypeNode.Name);

                            Dictionary<string, object> packageReferenceAttributes = MissionXMLReader.GetAttributes(packageOn);
                            packageTable.Reference = new PackageReference(Convert.ToInt32(packageReferenceAttributes["FromPluginID"]), packageReferenceAttributes["FromPackage"].ToString());
                        }
                        else if ("DynamicPackage" == packageOn.Name)
                        {
                            // dynamic packages can be a part of OUTPUT-packages only!
                            Debug.Assert("Output" == packageTypeNode.Name);
                            Debug.Assert(true == packageTable.IsSoftPackage);
                        }
                        else if ("Package" == packageOn.Name)
                        {
                            // is there any sequence definition?
                            XmlNodeList sequenceNodes = packageOn.SelectNodes("Sequence/Column");
                            if (sequenceNodes.Count > 0)
                            {
                                // sequence nodes can be a part of INPUT linked packages only!
                                Debug.Assert("Input" == packageTypeNode.Name);
                                Debug.Assert(true == packageTable.IsSoftPackage);

                                // select the column-sequence from this packages
                                foreach (XmlNode columnOn in sequenceNodes)
                                {
                                    string columnName = columnOn.Attributes.GetNamedItem("Name").Value;
                                    string columnType = columnOn.Attributes.GetNamedItem("Type").Value;
                                    MissionPluginPackageColumn column = new MissionPluginPackageColumn(columnName, Type.GetType(columnType, true, true));

                                    // is there any default-value? (optional!)
                                    if (null != columnOn.Attributes.GetNamedItem("DefaultValue"))
                                    {
                                        column.DefaultValue = columnOn.Attributes.GetNamedItem("DefaultValue").Value;
                                    }

                                    // is there any validation-rule? (optional!)
                                    // is there any column-description value? (optional!)
                                    foreach (XmlNode columnDefinitionOn in columnOn.SelectNodes("*"))
                                    {
                                        // todo: save more than one validation-rule and datastream-link
                                        column.ExtendedProperties.Add(columnDefinitionOn.Name, columnDefinitionOn.InnerText);
                                    }
                                    packageTable.Columns.Add(column);
                                }
                            }

                            // select all column links from this packages
                            XmlNodeList linkedColumnsNodeList = packageOn.SelectNodes("Data/LinkedColumns");
                            foreach (XmlNode linkedColumnOn in linkedColumnsNodeList)
                            {
                                foreach (MissionPluginPackageColumn columnOn in packageTable.Columns)
                                {
                                    XmlNode columnNode = linkedColumnOn.SelectSingleNode(columnOn.ColumnName.Replace(" ", ""));
                                    // is there no linked column defined, we will work with default values later
                                    if (null != columnNode)
                                    {
                                        Dictionary<string, object> packageColumnReferenceAttributes = MissionXMLReader.GetAttributes(columnNode);
                                        columnOn.ColumnReference = new PackageColumnReference(Convert.ToInt32(packageColumnReferenceAttributes["FromPluginID"]), packageColumnReferenceAttributes["FromPackage"].ToString(), packageColumnReferenceAttributes["FromColumn"].ToString());
                                    }
                                }
                            }

                            // select all data parameters from this packages
                            XmlNodeList rowNodeList = packageOn.SelectNodes("Data/Row");
                            foreach (XmlNode dataRowOn in rowNodeList)
                            {                                
                                DataRow packageRow = packageTable.NewRow();
                                // we are reading every row, column by column
                                foreach (MissionPluginPackageColumn columnOn in packageTable.Columns)
                                {
                                    XmlNode columnNode = dataRowOn.SelectSingleNode(columnOn.ColumnName);

                                    // while every column could contain reference-tags, we must read them element by element.
                                    string cellValue = "";
                                    Dictionary<string, object> cellReferenceAttributes = new Dictionary<string, object>(); 
                                    
                                    foreach (XmlNode childNode in columnNode.ChildNodes)
                                    {
                                        // is this childNode a static value (NO reference-tag!)?
                                        // ATTENTION: XmlNodeType.Text is downwardly compatible
                                        if (childNode.NodeType == XmlNodeType.CDATA || childNode.NodeType == XmlNodeType.Text)
                                        {
                                            cellValue += childNode.Value;
                                        }
                                        // or is this a reference-tag?
                                        else
                                        {
                                            // we must parse the reference-definition to save them as cell-reference
                                            Debug.Assert("GetValueByLoop" == childNode.Name, String.Format("{0} is an invalid tag! (you can use a \"GetValueByLoop\"-tag here only!)", childNode.Name));
                                            cellReferenceAttributes = GetAttributes(childNode);
                                            // we save a placeholder-template here, to know on which position the reference should used
                                            cellValue += "{#}";
                                        }                                        
                                    }

                                    // is there at least on cell reference, we should save the reference with his template string (which contains a placeholder for every reference)
                                    if (cellReferenceAttributes.Count > 0)
                                    {
                                        columnOn.ExtendedProperties.Add(MissionPluginPackageColumn.ExtendedPropertiesKey.CellReference, new PackageCellReference(Convert.ToInt32(cellReferenceAttributes["FromLoopID"]), cellReferenceAttributes["FromColumn"].ToString()));
                                    }
                                    // is there NO reference, we can save the cell value as static value
                                    else
                                    {
                                        packageRow[columnOn.ColumnName] = Uri.UnescapeDataString(cellValue);                                        
                                    }
                                }
                                packageTable.Rows.Add(packageRow);
                            }
                        }
                        else
                        {
                            Debug.Assert(false, "Invalid TagName!");
                        }
                    }
                }
            }
        }

        private static Dictionary<string, object> GetAttributes(XmlNode node)
        {
            Dictionary<string, object> attributes = new Dictionary<string, object>();
            foreach (XmlAttribute attributeOn in node.Attributes)
            {
                attributes.Add(attributeOn.Name, attributeOn.Value);
            }
            return attributes;
        }
    }
}
